package org.song.filter.certification;

import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.song.cache.UserInfoTokenCache;
import org.song.cache.UserMenuCache;
import org.song.exception.myException.CommonException;
import org.song.exception.result.AjaxResult;
import org.song.model.entity.MenuEntity;
import org.song.service.MenuService;
import org.song.util.JacksonUtil;
import org.song.util.JwtTokenUtil;
import org.song.util.TreeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @author song
 * @date 2022/3/10 9:22
 * 用户认证成功处理器
 **/
@Slf4j
@Component( value = "myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired( required = true )
    @Qualifier( value = "userInfoTokenCacheImpl")
    private UserInfoTokenCache userInfoTokenCache;

    @Autowired( required = true )
    @Qualifier( value = "jwtTokenUtil")
    private JwtTokenUtil jwtTokenUtil;

    @Autowired( required = true )
    @Qualifier( value = "menuServiceImpl" )
    private MenuService menuService;

    @Autowired( required = true )
    @Qualifier( value = "userMenuCacheImpl")
    private UserMenuCache userMenuCache;

    /**
     * 用户登录成功处理函数
     * @param request
     * @param response
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 回调获取用户信息
        final UserDetails currentUserInfo = (UserDetails) authentication.getPrincipal();
        Assert.isTrue( Objects.nonNull( currentUserInfo ) , () -> {
            log.error( "用户登录认证成功，userDetails回调为空\n" + JacksonUtil.getJsonString( request ));
            return new CommonException("用户登录认证成功，userDetails回调为空");
        });
        final String username = currentUserInfo.getUsername();
        //用户登录成功存入ThreadLocal
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 根据用户名去缓存中获取token，没有则说明过期重新生成token并返回，有直接复用，MyOncePerRequestFilter 会刷新用户token进行延时
        final String userToken = Optional.ofNullable(userInfoTokenCache.getOnlineUser(username)).orElseGet(() -> {
            log.info("用户： " + username + "token过时，重新生成token，写回缓存。");
            final String generateToken = jwtTokenUtil.generateToken(currentUserInfo);
            // 新token存入缓存默认7天
            userInfoTokenCache.setExOnlineUser(username, generateToken);
            return generateToken;
        });

        // 根据登录成功的用户名从缓存中取出菜单信息，过期从数据库取 写回缓存 TODO: 缓存穿透
        Set<MenuEntity> menus = Optional.ofNullable( userMenuCache.getUserMenuList( username ) ).orElseGet( () -> {
            log.info( "当前用户： " + username + "缓存中菜单信息过期从DB取回写会缓存");
            return menuService.getMenuListByUserName(username);
        } );

        // 对取出的menus菜单进行排序 ，使用 TreeUtil工具类
        List<MenuEntity> menuEntities = TreeUtil.parseMenuTree(menus);

        // 将该 用户名、权限、生成的token、用户对应的菜单存入 map中 并返回给客户端
        final Map<String, Object> userInfoMap = new HashMap<String, Object>( 4 ) {{
            put("username", username);
            put("auth", currentUserInfo.getAuthorities());
            put("menus", menuEntities);
            put("token", userToken);
        }};

        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Method", "POST,GET");
        response.getWriter().write( JacksonUtil.getJsonString( AjaxResult.success( userInfoMap ) ) );

    }

}
